package com.xinggq.pay.service.service.impl;

import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson2.JSON;
import com.xinggq.pay.service.context.AppIdContext;
import com.xinggq.pay.service.entity.AppCallbackConfigEntity;
import com.xinggq.pay.service.entity.CallbackRetryLogEntity;
import com.xinggq.pay.service.entity.PayCallbackEntity;
import com.xinggq.pay.service.entity.ChannelInteractionRecordEntity;
import com.xinggq.pay.service.entity.PayOrderEntity;
import com.xinggq.pay.service.enums.CallbackTypeEnum;
import com.xinggq.pay.service.enums.TradeTypeEnum;
import com.xinggq.pay.service.service.AppCallbackConfigService;
import com.xinggq.pay.service.service.AppCallbackService;
import com.xinggq.pay.service.service.CallbackRetryLogService;
import com.xinggq.pay.service.service.ChannelCallbackService;
import com.xinggq.pay.service.service.PayOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 应用回调服务实现类
 * @author xinggq
 * @since 2025/9/16
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AppCallbackServiceImpl implements AppCallbackService {

  private static final String SUCCESS = "success";

  private final AppCallbackConfigService appCallbackConfigService;
  private final CallbackRetryLogService callbackRetryLogService;
  private final RestTemplate restTemplate;
  private final ChannelCallbackService channelCallbackService;
  private final PayOrderService payOrderService;

  @Value("${app.callback.max-retry:3}")
  private int maxRetryCount;

  @Value("${app.callback.retry-interval:300}")
  private int retryInterval;

  @Override
  public void executeCallback(String tradeNo) {
    //先查询历史回调记录
    CallbackRetryLogEntity retryRecord = callbackRetryLogService.findLatestByTradeNo(tradeNo);
    if (retryRecord != null) {
      processRetryCallback(retryRecord);
      return;
    }
    // 获取回调信息
    PayCallbackEntity callbackEntity = channelCallbackService.findBySuccessTradeNo(tradeNo);
    Validator.validateNotNull(callbackEntity, "未查询到接收成功的支付渠道回调信息");
    // 补全appid，避免出错
    if (callbackEntity.getAppId() != null) {
      AppIdContext.setAppId(callbackEntity.getAppId());
    }
    // 获取回调配置
    AppCallbackConfigEntity config = getCallbackConfig(callbackEntity.getChannelType(), tradeNo);
    Validator.validateNotNull(config, "未找到app对应的回调配置");
    // 发起回调请求
    sendCallbackRequest(callbackEntity, config);
  }


  @Override
  @Async("taskExecutor")
  public void asyncExecuteCallback(String tradeNo) {
    try{
      executeCallback(tradeNo);
      log.info("成功发起异步回调，tradeNo:{}", tradeNo);
    } catch (Exception e) {
      log.error("异步执行应用回调异常，tradeNo:{}", tradeNo, e);
    }
  }

  @Override
  public void retryFailedCallbacks() {
    List<CallbackRetryLogEntity> retryRecords = callbackRetryLogService.findRetryRecords(
        LocalDateTime.now(), maxRetryCount);
    retryRecords.forEach(this::processRetryCallback);
    log.info("处理重试回调完成，共处理{}条记录", retryRecords.size());
  }


  /**
   * 获取回调配置
   * @param payChannel 支付渠道
   * @param tradeNo 交易号
   * @return 回调配置
   */
  private AppCallbackConfigEntity getCallbackConfig(String payChannel, String tradeNo) {
    // 根据交易号判断是支付还是退款
    CallbackTypeEnum callbackType = determineCallbackType(tradeNo);
    AppCallbackConfigEntity config = appCallbackConfigService.findEnabledByPayChannelAndType(payChannel, callbackType);
    Validator.validateNotNull(config, "未配置回调信息，支付渠道：" + payChannel + "，回调类型：" + callbackType.getDesc());
    return config;
  }
  
  /**
   * 根据交易号判断回调类型
   * @param tradeNo 交易号
   * @return 回调类型
   */
  private CallbackTypeEnum determineCallbackType(String tradeNo) {
    try {
      PayOrderEntity payOrder = payOrderService.findByTradeNo(tradeNo);
      if (payOrder != null && TradeTypeEnum.REFUND.getCode().equals(payOrder.getTradeType())) {
        return CallbackTypeEnum.REFUND;
      }
    } catch (Exception e) {
      log.warn("查询交易订单失败，tradeNo:{}", tradeNo, e);
    }
    // 默认返回支付类型
    return CallbackTypeEnum.PAY;
  }

  /** 构建渠道交互记录 */
  private ChannelInteractionRecordEntity buildChannelRecord(PayCallbackEntity callbackEntity,
      PayOrderEntity payOrder) {
    ChannelInteractionRecordEntity channelRecord = new ChannelInteractionRecordEntity();
    channelRecord.setId(callbackEntity.getId());
    channelRecord.setAppId(payOrder.getAppId());
    channelRecord.setChannelTradeNo(callbackEntity.getChannelTradeNo());
    channelRecord.setPayChannel(callbackEntity.getChannelType());
    channelRecord.setResponse(callbackEntity.getCallbackData());
    return channelRecord;
  }

  /** 构建回调数据 */
  private Map<String, Object> buildCallbackData(PayCallbackEntity callbackEntity) {
    Map<String, Object> data = new HashMap<>(4);
    data.put("merchantOutTradeNo", callbackEntity.getMerchantTradeNo());
    data.put("tradeNo", callbackEntity.getTradeNo());
    data.put("payChannel", callbackEntity.getChannelType());
    data.put("response", callbackEntity.getCallbackData());
    return data;
  }

  /** 发送回调请求 */
  private void sendCallbackRequest(PayCallbackEntity callbackEntity,
      AppCallbackConfigEntity config) {
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);
    Map<String, Object> callbackData = buildCallbackData(callbackEntity);
    HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(callbackData, headers);
    ResponseEntity<String> response = restTemplate.postForEntity(
        config.getPayCallbackUrl(), requestEntity, String.class);
    // 保存回调日志
    saveCallbackLog(callbackEntity, config, callbackData, response);

  }

  /** 保存回调日志 */
  private void saveCallbackLog(PayCallbackEntity callbackEntity, AppCallbackConfigEntity config,
      Map<String, Object> callbackData, ResponseEntity<String> response) {

    CallbackRetryLogEntity logEntity = new CallbackRetryLogEntity();
    logEntity.setTradeNo(callbackEntity.getTradeNo());
    logEntity.setMerchantOutTradeNo(callbackEntity.getMerchantTradeNo());
    logEntity.setAppId(config.getAppId());
    logEntity.setPayCallbackUrl(config.getPayCallbackUrl());
    logEntity.setRequestData(JSON.toJSONString(callbackData));
    String responseBody = response.getBody();
    if (responseBody != null && responseBody.length() > 255) {
      responseBody = responseBody.substring(0, 255);
    }
    logEntity.setResponseData(responseBody);
    logEntity.setCallCount(1);
    if (response.getStatusCode().is2xxSuccessful() && SUCCESS.equals(response.getBody())) {
      logEntity.setCallbackStatus(1);
    } else {
      logEntity.setCallbackStatus(0);
      logEntity.setNextRetryTime(LocalDateTime.now().plusSeconds(retryInterval));
    }
    callbackRetryLogService.saveCallbackLog(logEntity);
  }

  /** 处理重试回调 */
  private void processRetryCallback(CallbackRetryLogEntity retryRecord) {
    String responseBody = null;
    try {
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      HttpEntity<String> requestEntity = new HttpEntity<>(retryRecord.getRequestData(), headers);

      ResponseEntity<String> response = restTemplate.postForEntity(
          retryRecord.getPayCallbackUrl(), requestEntity, String.class);
      responseBody = response.getBody();
      if (responseBody != null && responseBody.length() > 255) {
        responseBody = responseBody.substring(0, 255);
      }
      if (response.getStatusCode().is2xxSuccessful() && SUCCESS.equals(response.getBody())) {
        updateRetrySuccessRecord(retryRecord, responseBody);
      } else {
        updateRetryFailRecord(retryRecord, responseBody);
      }
    } catch (Exception e) {
      updateRetryFailRecord(retryRecord, responseBody);
    }
  }

  private void updateRetrySuccessRecord(CallbackRetryLogEntity retryRecord, String response) {
    callbackRetryLogService.updateRetryRecord(retryRecord.getId(), response, 1, null);
  }

  private void updateRetryFailRecord(CallbackRetryLogEntity retryRecord, String response) {
    callbackRetryLogService.updateRetryRecord(retryRecord.getId(), response, 0, LocalDateTime.now()
        .plusSeconds((long) retryInterval * (retryRecord.getCallCount() + 1)));
  }
}